#include "../include/defs.h"
#include "../include/kc.h"
#include "../include/sched.h"
#include "../include/string.h"
#include "../include/malloc.h"
#include "../include/random.h"
#include "../include/io.h"
#include "../include/kasm.h"
#include "../include/kernel.h"

int last100[100]={0};
int counter100;
int FirstTime=1;
int LastPID;
int sched_alg;

extern int CurrentPID;
int FirstTime2=1;
extern PROCESS idle;
extern PROCESS procesos[];
extern int nextfree;
extern unsigned long *page_table2;
extern int page_pid[CANT_PAGES];


//Retorna True si no hay procesos libres
int NoProcesses()
{
    int i;
    for (i=0;i<MAX_PROC;i++)
    {
        if (procesos[i].free==0)
            return 0;
    }
    return 1;
}

int Idle(int argc, char* argv[])
{
    _Sti();
    while(1)
    {
        __asm__ __volatile__("hlt");
    }
}

//Funcion que almacena el ESP actual
void
SaveESP (int ESP)
{
	PROCESS* temp;

	if (!FirstTime)
	{
		temp=GetProcessByPID (CurrentPID);
		temp->ESP=ESP;
	}
	FirstTime = 0;
	return;

}


//Funcion que obtiene el ESP de idle para switchear entre tareas.
void* GetTemporaryESP (void)
{
    return (void*)idle.ESP;
}

//Funcion que devuelve el PROCESS* siguiente a ejecutar
PROCESS* GetNextProcess (void)
{

    PROCESS* temp, *actual;

    //selecciona la tarea
    if ( sched_alg == SCHED_PRIORITY )
    {
        temp=schedWithPriority();
        temp->lastCalled=0;
    }
    else if ( sched_alg == SCHED_LOTTERY )
    {
        temp=lottery();
        temp->lastCalled=0;
    }
    else
    {
        printf("ERROR: Sched alg incorrecto\n");
    }

    actual = GetProcessByPID (CurrentPID);
    actual->next_free = nextfree;
    nextfree = temp->next_free;
    LastPID = actual->pid;

    CurrentPID=temp->pid;
    last100[counter100]=CurrentPID;
    counter100=(counter100+1)%100;
    return temp;

}

PROCESS* lottery(void)
{
    int i;
    int winner;
    int winnervalue=-1;
    int ticket;

    ticket = randomInt();

    if (NoProcesses())
        return &idle;

    for( i = 0 ; i < MAX_PROC ; i++ )
    {
        if(!procesos[i].free)
        {
            if(procesos[i].sleep >= 0)
            {
                if(procesos[i].blocked == 0)
                {
                    procesos[i].lastCalled++;
                    int temp;
                    temp=(MAX_PRIORITY-procesos[i].priority) * PRIORITY_RATIO *
                        ticket+procesos[i].lastCalled;

                    if (temp>winnervalue)
                    {
                        winnervalue=temp;
                        winner=i;
                    }
                }
            }
            else
                procesos[i].sleep++;
        }
    }

    if (winnervalue>-1)
        return &procesos[winner];
    else
        return &idle;

}
void
setSched(int sched)
{
    sched_alg = sched;
    return;
}

PROCESS* schedWithPriority(void)
{
    int i;
    int winner;
    int winnervalue=-1;

    if (NoProcesses())
        return &idle;

    for( i = 0 ; i < MAX_PROC ; i++ )
    {
        if(!procesos[i].free)
        {
            if(procesos[i].sleep >= 0)
            {
                if(procesos[i].blocked == 0)
                {
                    procesos[i].lastCalled++;
                    int temp;
                    temp=(MAX_PRIORITY-procesos[i].priority)*PRIORITY_RATIO +
                            procesos[i].lastCalled;

                    if (temp>winnervalue)
                    {
                        winnervalue=temp;
                        winner=i;
                    }
                }
            }
            else
                procesos[i].sleep++;
        }
    }

    if (winnervalue>-1)
        return &procesos[winner];
    else
        return &idle;

}

//Funcion que devuelve el ESP del proceso actual.
int
LoadESP(PROCESS* proc)
{
    int i;
    PROCESS* lastProc;

    /* Deshabilito las paginas "viejas" */
    if(LastPID > 0)
    {
        lastProc = GetProcessByPID(LastPID);

        for (i = 0; i <CANT_PAGES ; i++)
        {
            if(page_pid[i] == LastPID || ( lastProc->parent != 0 && page_pid[i] == lastProc->parent))
            {
                page_table2[i] = page_table2[i] & 0xFFFFFFFE;
            }
        }
    }

    for (i = 0; i <CANT_PAGES ; i++)
    {
        if(page_pid[i] == CurrentPID || page_pid[i] == proc->parent)
        {
            /* Habilito las "nuevas" */
            page_table2[i] = page_table2[i] | 3;
        }
    }

    if (proc->ESP - proc->stackstart <= 500 && proc->pid >= 9)
    {
//        printf("LOADESP: %d\nLOADSTART: %d\ndif: %d\n", proc->ESP, proc->stackstart, proc->ESP - proc->stackstart);
        /* Verifico cuanto me queda de stack. Si es "poco" le asigno otra pagina */
        proc->ESP = newStack(proc);
    }

    return proc->ESP;
}

//Funcion a donde van a parar las funciones que terminan.
void Cleaner(void)
{
    _Cli();
    Destroy(CurrentPID);
    _Sti();

    while(1);

}

//Funcion que inicializa la tarea IDLE
void
SetupScheduler(void)
{
    void* idleprocess;
    int i;

    for (i=0;i<MAX_PROC;i++)
        procesos[i].free=1;

    idle.stack_page = 1;
    idle.heap_page = 0;

    page_pid[0] = 0;
    page_pid[1] = 0;

    idle.next_free = 4 * 1024 * 1024 + 4096*idle.stack_page;
    nextfree = idle.next_free;

    page_table2[idle.stack_page] = page_table2[idle.stack_page] | 3;
    page_table2[idle.heap_page] = page_table2[idle.heap_page] | 3;

    idleprocess= Malloc(0x200);
    idle.pid=0;
    idle.foreground=0;
    idle.priority=4;
    memcpy(idle.name,"Idle",5);
    idle.sleep=0;
    idle.blocked=0;
    idle.tty=0;


    idle.stackstart=(int)idleprocess;
    idle.stacksize=0x200;
    //procesos[i].parent=0;
    idle.parent=0;
    idle.lastCalled=0;
    idle.ESP=LoadStackFrame(Idle,0,0,(int)(idleprocess+0x1FF),Cleaner);
    nextfree = 0x200000;

    /*Default sched alg is SCHED_PRIORITY*/
    sched_alg = SCHED_PRIORITY;

    return;
}

void debug()
{
    return;
}

int newStack(PROCESS * proc)
{
    int i;
    int new_start, new_ESP;
    void * old_stack, * new_stack;

    /* Busco la ultima ocupada y me paro en la siguiente */
    for(i = CANT_PAGES; i > 0; i-- )
    {
        if(page_pid[i-1] != -1)
        {
            new_start = i;
            break;
        }
    }

    debug();

    /* Habilito las nuevas paginas donde voy a copiar el contenido del stack */
    for(i = new_start; i <= new_start + proc->stackpages; i++)
    {
        page_pid[i] = CurrentPID;
        page_table2[i] = page_table2[i] | 3;
    }

    old_stack = (void *) (page_table2[proc->stack_page] & 0xFFFFF000);
    new_stack = (void *) (page_table2[new_start+1] & 0xFFFFF000);

    /* Copio el contenido */
    memcpy(new_stack, old_stack, 4096 * proc->stackpages);



    /* actualizo los campos del proc */

    proc->stacksize += 4096;
    proc->stackstart = page_table2[new_start] & 0xFFFFF000;

    /* Calculo el nuevo ESP */
    new_ESP = (proc->ESP % 4096) + (int)new_stack;
    ((STACK_FRAME *) new_ESP)->EBP = (((STACK_FRAME *)(proc->ESP))->EBP % 4096)+ (int)new_stack;


    labEBP((int *)(((STACK_FRAME *) new_ESP)->EBP),
            (int *)(((STACK_FRAME *) proc->ESP)->EBP), (int)new_stack, proc->stack_page);


    /* Deshabilito las paginas viejas de stack */
    for(i = proc->stack_page; i < proc->stack_page + proc->stackpages; i++)
    {
        page_pid[i] = -1;
        page_table2[i] = page_table2[i] & 0xFFFFFFFE;
    }

    proc->stackpages++;
    proc->stack_page = new_start;



    return new_ESP;
}

void labEBP(int * in, int * out, int offset, int old_ind)
{
    if(*out == 0)
    {
        *in = *out;
        return;
    }

    *in = (*out % 4096)+ offset + (4096 * ((((*out)/4096) - 1024) - old_ind));

    labEBP((int*)*in, (int*)*out, offset, old_ind);
    return ;
}
